#include <lc_test_char_traits.h>

#ifndef BUFSIZ
# define BUFSIZ (1024)
#endif /* BUFSIZ */

static const Char* charStrings[] = {
  "Hello world!",
  "testing char traits!",
  " ",
  "     64",
  "iuh((*Y&^SR&UAS|b\n\n\n\n\n\adaiufhaidfa\adaf\b",
  nullptr
};

static const WChar* wcharStrings[] = {
  L"Hello world!",
  L"symbolsøőșż❂❊₡¤τ⠴⠬⠘",
  L"AåB∫CçD∂",
  L"           ",
  L"64",
  L"iuh((*Y&^SR&UAS|b\n\n\n\n\n\adaiufhaidfa\adaf\b",
  nullptr
};

Size getRandomIndex(Size limit) {
  return Size(::std::rand() % limit);
}

template <typename C>
const C* getRandomString() {
  return nullptr;
}

template <>
const Char* getRandomString() {
  Size n = 0;
  while (charStrings[n] != nullptr) n++;
  return charStrings[getRandomIndex(n)];
}

template <>
const WChar* getRandomString() {
  Size n = 0;
  while (wcharStrings[n] != nullptr) n++;
  return wcharStrings[getRandomIndex(n)];
}

TYPED_TEST(TestCharTraits, CharacterComparision) {
  const TypeParam c1 = *getRandomString<TypeParam>();
  const TypeParam c2 = *getRandomString<TypeParam>();

  EXPECT_EQ(c1 == c2, CharTraits<TypeParam>::eq(c1, c2))
      << "CharTraits::eq result not same as == operator";
  EXPECT_EQ(c1 < c2, CharTraits<TypeParam>::lt(c1, c2))
      << "CharTraits::lt result not same as < operator";
}

TYPED_TEST(TestCharTraits, StringLength) {
  const TypeParam* str = getRandomString<TypeParam>();
  Size n = 0;
  while (str[n] != TypeParam()) n++;

  EXPECT_EQ(n, CharTraits<TypeParam>::length(str))
      << "String length determination failed";
  EXPECT_EQ(0U, CharTraits<TypeParam>::length(nullptr))
      << "String length determination of null string failed";
}

TYPED_TEST(TestCharTraits, StringComparision) {
  const TypeParam* str = getRandomString<TypeParam>();
  const Size n = CharTraits<TypeParam>::length(str);
  EXPECT_EQ(0, CharTraits<TypeParam>::compare(str, str, n))
      << "Comparing similar strings failed";

  const TypeParam* str1 = getRandomString<TypeParam>();
  const Size n1 = CharTraits<TypeParam>::length(str1);
  const TypeParam* str2 = getRandomString<TypeParam>();
  const Size n2 = CharTraits<TypeParam>::length(str2);
  const Size compareLength = n2 > n1 ? n1 : n2;

  const Int compare = CharTraits<TypeParam>::compare(str1, str2, compareLength);
  if (compare != 0) {
    EXPECT_STRNE(str1, str2)
        << "Comparing two random strings failed";
  } else {
    EXPECT_STREQ(str1, str2)
        << "Comparing two random strings failed";
  }
  
  EXPECT_NO_FATAL_FAILURE(CharTraits<TypeParam>::compare(nullptr, str, 104903))
      << "Crash on invalid data";
  EXPECT_NO_FATAL_FAILURE(CharTraits<TypeParam>::compare(str1, str, 104903))
      << "Crash on invalid data";
  EXPECT_NO_FATAL_FAILURE(CharTraits<TypeParam>::compare(0, 0, 104903))
      << "Crash on invalid data";
  EXPECT_NO_FATAL_FAILURE(CharTraits<TypeParam>::compare(str2, str1, -4302))
      << "Crash on invalid data";
}

TYPED_TEST(TestCharTraits, CharacterAssignmentAndFill) {
  TypeParam str[128];
  TypeParam c = *getRandomString<TypeParam>();
  const Size n = 23;

  EXPECT_EQ(str, CharTraits<TypeParam>::assign(str, n, c))
      << "Character fill failed";
  for (Size i = 0; i < n; ++i)
    EXPECT_EQ(str[i], c) << "Character fill failed";
  EXPECT_NO_FATAL_FAILURE(CharTraits<TypeParam>::assign(nullptr, 1024U, c))
      << "Crash on invalid data";

  TypeParam a, b = c;
  CharTraits<TypeParam>::assign(a, b);
  EXPECT_TRUE(TypeParam(a) == TypeParam(b)) << "Character assignment failed";
}

TYPED_TEST(TestCharTraits, CharacterFind) {
  const TypeParam* str = getRandomString<TypeParam>();
  const Size n = CharTraits<TypeParam>::length(str);

  Size idx = Size(::std::rand() % n);
  EXPECT_EQ(str[idx], *CharTraits<TypeParam>::find(str, n, str[idx]))
      << "Find first occurrence of character failed";
  
  idx = Size(::std::rand() % n);
  EXPECT_EQ(str[idx], *CharTraits<TypeParam>::find(str, n, str[idx]))
      << "Find first occurrence of character failed";

  idx = Size(::std::rand() % n);
  EXPECT_EQ(str[idx], *CharTraits<TypeParam>::find(str, 2 * n, str[idx]))
      << "Find first occurrence of character (with invalid length) failed";

  idx = Size(::std::rand() % n);
  EXPECT_NE(str + idx, CharTraits<TypeParam>::find(str, idx, str[idx]))
      << "Find first occurrence of character (with invalid length) failed";

  TypeParam c = TypeParam(::std::rand() % NumericLimits<TypeParam>::maximum());
  const TypeParam* fstr = CharTraits<TypeParam>::find(str, n, c);
  if (fstr)
    EXPECT_EQ(c, *fstr) << "Find first occurrence of random character failed";
}

TYPED_TEST(TestCharTraits, StringCopy) {
  const TypeParam* str1 = getRandomString<TypeParam>();
  const Size n = CharTraits<TypeParam>::length(str1);
  TypeParam str2[BUFSIZ];

  EXPECT_EQ(str2, CharTraits<TypeParam>::copy(str2, str1, n))
      << "Unexpected return value";
  str2[n] = TypeParam();
  EXPECT_STREQ(str1, str2) << "String copy failed";

  Size idx = ::std::rand() % n;
  TypeParam* str3 = &str2[idx];
  const Size n3 = CharTraits<TypeParam>::length(str3);
  EXPECT_EQ(str2, CharTraits<TypeParam>::copy(str2, str3, n3))
      << "Unexpected return value";
  str2[n3] = TypeParam();
  EXPECT_TRUE(str2 + (str3 - str2) == str3)
      << "String copy of non-disjunct strings failed";
}

TYPED_TEST(TestCharTraits, StringMove) {
  const TypeParam* str1 = getRandomString<TypeParam>();
  const Size n = CharTraits<TypeParam>::length(str1);
  TypeParam str2[BUFSIZ];

  EXPECT_EQ(str2, CharTraits<TypeParam>::move(str2, str1, n))
      << "Unexpected return value";
  str2[n] = TypeParam();
  EXPECT_STREQ(str1, str2) << "String move failed";

  Size idx = ::std::rand() % n;
  TypeParam* str3 = &str2[idx];
  const Size n3 = CharTraits<TypeParam>::length(str3);
  EXPECT_EQ(str2, CharTraits<TypeParam>::move(str2, str3, n3))
      << "Unexpected return value";
  str2[n3] = TypeParam();
  EXPECT_TRUE(str2 + (str3 - str2) == str3)
      << "String move of non-disjunct strings failed";
  
}

TYPED_TEST(TestCharTraits, ConversionToMbs) {
  const TypeParam* str = getRandomString<TypeParam>();
  const Size n1 = CharTraits<TypeParam>::length(str);
  Bool expectFailure = false;
  Char mbstr[BUFSIZ];

  for (Size i = 0; i < n1; ++i)
    expectFailure |= !CharTraits<TypeParam>::isAscii(str[i]);

  Size n2 = CharTraits<TypeParam>::toMbs(mbstr, str, n1);
  ASSERT_EQ(expectFailure, Size(-1) == n2)
      << "Conversion to mbs failed";

  if (!expectFailure)
    EXPECT_TRUE(n1 <= CharTraits<Char>::length(mbstr))
        << "Conversion to mbs failed";
}

TYPED_TEST(TestCharTraits, ConversionToWcs) {
  const TypeParam* str = getRandomString<TypeParam>();
  const Size n1 = CharTraits<TypeParam>::length(str);
  WChar wcstr[BUFSIZ];

  Size n2 = CharTraits<TypeParam>::toWcs(wcstr, str, n1);
  ASSERT_NE(Size(-1), n2)
      << "Conversion to wcs failed";
  EXPECT_TRUE(n2 <= CharTraits<WChar>::length(wcstr))
      << "Conversion to wcs failed";
}
